package cs4game;


import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.Stack;

import javax.swing.Timer;

public class MazeGen {
	
	private static final long serialVersionUID = 1L;
	private MazeCell[][] cellArray; //New array containing all of the cells in the maze
	private int height, width; //the height and width (num of rows and columns)
	private Stack<MazeCell> cellStack; //The stack for solving
	private Timer t; //New timer for solving the maze
	private MazeCell solvedCell; //A cell to store the cell which is the final
	private static final int BLANK=0, VISITED=1, DEAD=2; //Status values for each cell
	@SuppressWarnings("unused")
	private static final int UP = 0, RIGHT = 1, DOWN=2, LEFT = 3; //Directional values within the cell
	private boolean solved = false, wantsFocus = true; //Booleans to store whether the mazed is solved or wants the keyboard focus.
	private int startRow, startCol, endRow, endCol; //ints representing the location of the start and end cells.
	private boolean generating;

	public MazeGen(){

		cellStack = new Stack<MazeCell>();  //A new stack used when solving the maze.
	}
	public void setDimensions(int h, int w){
		//Sets the dimensions of the maze, which is the number of rows and columns.
		height = h;
		width = w;
		
		//Makes a new cellArray with the specified rows and columns
		cellArray = new MazeCell[h][w];
		
		//Adds a new mazeCell to each spot in the array and in the gridlayout, making them all ignore repaint.
		for(int i = 0; i<height; i++)
			for(int j = 0; j<width; j++){
				cellArray[i][j] = new MazeCell(i, j, true, cellArray);
			}
	}
	
	public boolean isValid(int r, int c){
		//if the square is outside of the grid, return false
		if(r<0 || c<0 || r>=height || c>=width) 
			return false;
		return true; //Otherwise, return true
	}

	public void randomMaze(){
		//Generates a random start and end coordinates, and makes a random maze using these.
		int randSR = (int)(Math.random()*height), randSC = (int)(Math.random()*width);
		int randER = (int)(Math.random()*height), randEC = (int)(Math.random()*width);
		randomMaze(randSR, randSC, randER, randEC);
	}

	public void randomMaze(int sR, int sC, int eR, int eC){
		//Set the fields for the start and end cells as the given values
		startRow = sR; 
		startCol = sC;
		endRow = eR;
		endCol = eC;
		
		//Say that its generating a maze right now
		generating = true;
		
		//Set new values for the start and end cells as given.
		MazeCell startCell = cellArray[startRow][startCol];
		MazeCell endCell = cellArray[endRow][endCol];
		
		//Make a new stack, used for construction instead of solving, and push the first cell to it.
		Stack<MazeCell> addingStack = new Stack<MazeCell>();
		addingStack.push(startCell);
		do{
			//If it isn't generating, return.
			if(!generating)
				return;
			//Make a new arraylist to store the current cells neighbors.
			//Only add the cell if it exists and isn't dead or visited.
			ArrayList<MazeCell> neighbors = new ArrayList<MazeCell>();
			if(validToAdd(addingStack.peek().row()-1, addingStack.peek().col())){
				neighbors.add(cellArray[addingStack.peek().row()-1][addingStack.peek().col()]);
			}
			if(validToAdd(addingStack.peek().row()+1, addingStack.peek().col())){
				neighbors.add(cellArray[addingStack.peek().row()+1][addingStack.peek().col()]);
			}
			if(validToAdd(addingStack.peek().row(), addingStack.peek().col()-1)){
				neighbors.add(cellArray[addingStack.peek().row()][addingStack.peek().col()-1]);
			}
			if(validToAdd(addingStack.peek().row(), addingStack.peek().col()+1)){
				neighbors.add(cellArray[addingStack.peek().row()][addingStack.peek().col()+1]);
			}

			if(neighbors.isEmpty()){
				//If there are no neighbors, pop the current cell from the stack and set it as dead.
				addingStack.pop().setStatus(DEAD);
			}
			else{
				//If there are neighbors, pick a random one.
				int randCell = (int)(Math.random()*neighbors.size());
				
				//Set the current cell as visited, remove the borders between the to, and add the new to the stack.
				addingStack.peek().setStatus(VISITED);
				addingStack.peek().setBoth(addingStack.peek().directionTo(neighbors.get(randCell)),false);
				addingStack.push(neighbors.get(randCell));
			}



		}while(addingStack.peek()!= startCell); //While you haven't gotten back to the beginning

		//Set the statuses of all the cells as blank.
		for(int i = 0; i<height; i++){
			for(int j = 0; j<width; j++){
				cellArray[i][j].setStatus(BLANK);
			}
		}
		//Push the start cell to the solving stack, set the solved cell as the endcell, and the startcell as the start.
		cellStack.push(startCell);
		solvedCell = endCell;
		startCell.setStart();
		endCell.setIsEnd(true);	
	}
	
	private boolean validToAdd(int r, int c) {
		//If it is outside the grid.
		if(!isValid(r, c)) 
			return false;

		//If the square has been traveled in or is marked as dead, return false
		if(cellArray[r][c].isVisited() || cellArray[r][c].isDead())
			return false;

		return true;
	}
	
	public boolean isValidStep(int r, int c){
		//if the square isn't valid
		if(!isValid(r, c)) 
			return false;

		//If the square has been traveled in or is marked as dead, return false
		if(cellArray[r][c].isVisited() || cellArray[r][c].isDead())
			return false;

		//If there is a line between the two squares, return false
		if(cellStack.peek().blocked(cellStack.peek().directionTo(cellArray[r][c])))
			return false;

		return true; //Otherwise, return true
	}
	
	public MazeCell[][] getCellArray() {return cellArray;}
}
